using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory.Visitors;
using AstHelpers;
using Genuilder;

namespace PropertiesExtractor.Targets
{

	public class PropertiesGenerator : AbstractAstVisitor, ICodeGenerator
	{
		public class Context
		{
			public Stack<NamespaceDeclaration> NsStack = new Stack<NamespaceDeclaration>();
			public Stack<TypeDeclaration> TypeStack = new Stack<TypeDeclaration>();
			public TypeDeclaration CurrentType
			{
				get
				{
					return TypeStack.Peek();
				}
			}
			public bool HasCurrentType
			{
				get
				{
					return TypeStack.Count > 0;
				}
			}
			public NamespaceDeclaration CurrentNamespace
			{
				get
				{
					return NsStack.Peek();
				}
			}
			public bool HasCurrentNamespace
			{
				get
				{
					return NsStack.Count > 0;
				}
			}
		}
		private readonly CompilationUnit _CompilationUnit = new CompilationUnit();
		public CompilationUnit CompilationUnit
		{
			get
			{
				return _CompilationUnit;
			}
		}

		public PropertiesGenerator()
		{
			_CompilationUnit = new CompilationUnit();
			_UsingDeclaration = new UsingDeclaration(new List<Using>());
			_CompilationUnit.Children.Add(_UsingDeclaration);
		}
		Context context = new Context();

		UsingDeclaration _UsingDeclaration;

		void EnsureUsingIsPresent(Using @using)
		{
			if(!_UsingDeclaration.Usings.Any(u => u.Name == @using.Name))
			{
				_UsingDeclaration.Usings.Add(@using);
			}
		}
		public override object VisitCompilationUnit(CompilationUnit compilationUnit, object data)
		{
			var newUsings = compilationUnit.Children.OfType<UsingDeclaration>().SelectMany(d => d.Usings).ToList();
			foreach(var newUsing in newUsings)
			{
				EnsureUsingIsPresent(newUsing);
			}
			return base.VisitCompilationUnit(compilationUnit, data);
		}

		public override object VisitTypeDeclaration(ICSharpCode.NRefactory.Ast.TypeDeclaration typeDeclaration, object data)
		{
			if(context.HasCurrentType)	//Does not support nested type yet
				return null;

			var attributes = typeDeclaration.Attributes.SelectMany(s => s.Attributes);
			var extractMetadata = attributes.FirstOrDefault(a => a.Name.EndsWith(typeof(ExtractProperties).Name));
			if(extractMetadata == null)
				return null;

			var typeIsPartial = (typeDeclaration.Modifier | Modifiers.Partial) == typeDeclaration.Modifier;
			var metadataModifiers = Modifiers.Abstract | (typeDeclaration.Modifier & (Modifiers)15); //Abstract and same visibility
			if(typeIsPartial)
				metadataModifiers = metadataModifiers | Modifiers.Partial;


			var typeName = extractMetadata.PositionalArguments.Count > 0 ? ((TypeOfExpression)extractMetadata.PositionalArguments[0]).TypeReference.Type : typeDeclaration.Name + "Properties";


			var metadataType = new TypeDeclaration(metadataModifiers, new List<AttributeSection>());
			metadataType.Name = typeName;
			if(context.HasCurrentNamespace)
				context.CurrentNamespace.AddChild(metadataType);
			else
				_CompilationUnit.AddChild(metadataType);
			//if(typeDeclaration.BaseTypes != null && typeDeclaration.BaseTypes.Count > 0)
			//{
			//    type.BaseTypes = new List<TypeReference>();
			//    type.BaseTypes.Add(new TypeReference(typeDeclaration.BaseTypes[0].Type + "Metadata"));
			//}
			context.TypeStack.Push(metadataType);
			base.VisitTypeDeclaration(typeDeclaration, data);
			context.TypeStack.Pop();

			return null;
		}

		public override object VisitNamespaceDeclaration(ICSharpCode.NRefactory.Ast.NamespaceDeclaration namespaceDeclaration, object data)
		{
			context.NsStack.Push(new NamespaceDeclaration(namespaceDeclaration.Name));
			base.VisitNamespaceDeclaration(namespaceDeclaration, data);
			_CompilationUnit.AddChild(context.CurrentNamespace);
			context.NsStack.Pop();
			return null;
		}
		public override object VisitPropertyDeclaration(ICSharpCode.NRefactory.Ast.PropertyDeclaration propertyDeclaration, object data)
		{
			var currentType = context.CurrentType;
			var metadataProp = new PropertyDeclaration(Modifiers.Public | Modifiers.Static, new List<AttributeSection>(), propertyDeclaration.Name, new List<ParameterDeclarationExpression>());
			metadataProp.TypeReference = new TypeReference(typeof(String).FullName);
			metadataProp.GetRegion = new PropertyGetRegion(new BlockStatement(), new List<AttributeSection>());
			metadataProp.GetRegion.Block.AddChild(new ReturnStatement(new PrimitiveExpression(propertyDeclaration.Name)));
			currentType.AddChild(metadataProp);
			return null;
		}
	}

}
